import threading
import time
import math, os, sys, random

from ks3.connection import Connection
from filechunkio import FileChunkIO
from datetime import datetime, timedelta
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QTableWidget, QTableWidgetItem, QMessageBox, QProgressBar
from PyQt5.QtCore import *
from PyQt5.Qt import QFileDialog, QBrush, QColor
from PyQt5 import uic, QtCore
from video_preview import videoPlayer
import pyperclip
import cv2
from concurrent.futures import ThreadPoolExecutor, as_completed

ACCESS_KEY = '';
SECRET_KEY = '';
REGION_ENDPOINT = "";
BUCKET_NAME = "";
# 金山云前缀链接
KS3_LINK = "";


# 渲染文件上传进度
class updateUIProgress(QtCore.QThread):
    # 通过类成员对象定义信号对象
    _signal = pyqtSignal(int)

    def __init__(self, current_val, total_val):
        super(updateUIProgress, self).__init__()
        self.total_val = total_val;
        self.current_val = current_val;

    def __del__(self):
        self.wait()

    def run(self):
        self._signal.emit(round(self.current_val/self.total_val*100));


#金山云的
class ks3:
    def __init__(self, block_threshold=50,
                 partsize=50,
                 files_thread_count=10,
                 part_thread_num=10):
        self.block_threshold_bytes = block_threshold * 1024 * 1024
        self.partsize = partsize * 1024 * 1024
        self.retry_times = 5
        self.files_thread_count = files_thread_count
        self.part_thread_num = part_thread_num;
        self.init_ks3();


    def init_ks3(self):
        #初始化ks3
        conn = Connection(ACCESS_KEY, SECRET_KEY, host=REGION_ENDPOINT, is_secure=False, domain_mode=False, timeout=1)
        self.conn= conn;
        bucket = conn.get_bucket(BUCKET_NAME);
        self.bucket = bucket;
        currentMonth = datetime.now().strftime("%Y%m");
        current_bucket_name = "qay-slm-2021ztjs/learningMaterial/" + currentMonth + "/";
        self.current_bucket_name = current_bucket_name;

    # 展示所有的文件
    def file_list(self, fileList_tableView: QTableWidget):
        files = self.bucket.list(prefix=self.current_bucket_name, max_keys=10);
        row = 0;
        for file in files:
            fileList_tableView.setItem(row, 0,
                                            QTableWidgetItem(
                                                file.name.replace(self.current_bucket_name, "")));
            fileList_tableView.setItem(row, 1, QTableWidgetItem(file.name));
            fileList_tableView.setItem(row, 2, QTableWidgetItem(str(round(file.size / 1024 / 1024, 2))));
            # 获取视频文件的网络时间
            #fileList_tableView.setItem(row, 3, QTableWidgetItem(str(round(self.video_duration_3(file.name)))));
            fileList_tableView.setItem(row, 4,
                                            QTableWidgetItem(self.timeUTC_handler(file.last_modified)));
            item_preview = QTableWidgetItem("预览");
            item_preview.setWhatsThis(file.name)
            item_preview.setData(5, file.name);
            item_preview.setTextAlignment(-1);
            item_preview.setForeground(QBrush(QColor(36, 64, 179)));
            fileList_tableView.setItem(row, 5, item_preview);
            item_download = QTableWidgetItem("下载");
            item_download.setWhatsThis(file.name)
            item_download.setData(6, file.name);
            item_download.setTextAlignment(-1);
            item_download.setForeground(QBrush(QColor(36, 64, 179)));
            fileList_tableView.setItem(row, 6, item_download);
            item_copylink = QTableWidgetItem("复制链接");
            item_copylink.setWhatsThis(file.name)
            item_copylink.setData(7, file.name);
            item_copylink.setTextAlignment(-1);
            item_copylink.setForeground(QBrush(QColor(36, 64, 179)));
            fileList_tableView.setItem(row, 7, item_copylink);
            row += 1;
        # 需要刷新一下
        fileList_tableView.repaint();

    def timeUTC_handler(self, target_time: str):
        """
            UTC世界标准时间（包含T和Z） 转 北京时间
            :param target_time:
            :return:
            """
        _date = datetime.strptime(target_time, "%Y-%m-%dT%H:%M:%S.%fZ")
        local_time = _date + timedelta(hours=8)
        end_time = local_time.strftime("%Y-%m-%d %H:%M:%S")
        return end_time

    # 针对每一块的上传任务
    def upload_part_task(self, mp, file_path, ks3_key, offset, chunk_size, part_num, current_part, total_part):
        """
        :param mp: KS3 会话实例
        :param file_path: 本地文件名
        :param ks3_key: ks3存储的文件名
        :param offset: 起始字节点
        :param chunk_size: 块大小
        :param part_num: 块儿ID
        :param retry_times: 单块失败重试次数
        :param current_part: 当前是第几块
        :param total_part: 总块数
        :return:
        """
        cur_task_ret = False
        try:
            for i in range(self.retry_times):
                try:
                    with FileChunkIO(file_path, 'rb', offset=offset, bytes=chunk_size) as fp:
                        mp.upload_part_from_file(fp, part_num=part_num)
                    cur_task_ret = True
                    if i:
                        print("%s  ----->  %d 块 重试 %s 次 上传成功" % (ks3_key, part_num, i))
                    else:
                        print("%s  ----->  %d 块  上传成功" % (ks3_key, part_num))
                        print("%d,  %d"%(current_part, total_part))
                    break
                except BaseException  as e:
                    print("%s 块 %d 上传ID =%s, 失败=%s" % (
                        ks3_key, part_num, mp.id, e))
                    if i + 1 >= self.retry_times:
                        raise e
        except BaseException as e:
            cur_task_ret = False
        finally:
            newtp = ThreadPoolExecutor(max_workers=2)
            newtp.submit(self.call_progressui, (round(1/total_part*100)))
            return {part_num: cur_task_ret}

    # 上传文件
    def uploadFile(self, filePath, progressBar: QProgressBar, fileList_tableView: QTableWidget):
        self.progressBar=progressBar;
        self.fileList_tableView = fileList_tableView;
        # 分块任务列表
        thread_list = []
        # 每个块儿的上传结果
        multi_chunk_result = {}
        source_size = os.stat(filePath).st_size;  # 文件大小
        file_suffix = os.path.splitext(filePath)[-1];  # 文件后缀名
        print("大小%d"%round(source_size/1024/1024, 2));
        # 举例以 50 MiB 为分块大小
        chunk_size = self.partsize
        chunk_count = int(math.ceil(source_size * 1.0 / chunk_size * 1.0))
        fileName = self.generate_file_id();
        myKeyName = self.current_bucket_name + fileName + file_suffix;
        # 初始化分块。获取初始化的 uploadId，之后的操作中将会用到
        # 如需在初始化分块时设置文件存储类型，请在 initiate_multipart_upload 中设置相关 headers
        # x-kss-storage-class有效值为"STANDARD"、"STANDARD_IA"。"STANDARD"表示标准存储，"STANDARD_IA"表示低频存储，如果不指定，默认为标准存储。
        headers = {"x-kss-storage-class": "STANDARD",
                   "kss-location-size": str(round(source_size/1024/1024, 2)),
                   "kss-location-video-duration": str(round(self.video_duration_local(filePath))),
                   "x-kss-callbackbody": "objectKey=${key}&etag=${etag}&uid="+fileName}

        mp = self.bucket.initiate_multipart_upload(myKeyName, headers=headers, policy="public-read");
        if not mp:
            print("%s 开始大文件上传, 上传ID=%s" % (myKeyName, mp.id))
        pool_args_list = []
        try:
            for i in range(chunk_count):
                offset = chunk_size * i
                bs = min(chunk_size, source_size - offset)
                part_num = i + 1
                # 将一个文件划分的所有块儿任务，添加到任务列表
                pool_args_list.append((mp, filePath, myKeyName, offset, bs, part_num, i, chunk_count))

            # 构建线程池实例
            tp = ThreadPoolExecutor(max_workers=self.part_thread_num)
            # 全部添加到任务队列开始处理
            [thread_list.append(tp.submit(self.upload_part_task, *args)) for args in pool_args_list]
            # 等待所有线程结束，获取全部线程的执行结果
            #[multi_chunk_result.update(part_thread.result()) for part_thread in as_completed(thread_list)]
            current_part=0;
            for part_thread in as_completed(thread_list):
                #print("%s这个应该每一个块的完成：%s "%(.state));
                current_part += 1;
                multi_chunk_result.update(part_thread.result())

            # 上传总结
            # 如果任务数和块儿数对不上，报一下出入
            if len(multi_chunk_result) != chunk_count:
                raise RuntimeError("%s 文件块缺失, expect=%d,actual=%d" % (myKeyName, chunk_count, len(multi_chunk_result)))
            # 如果任务都完毕，检查是否有失败的块儿
            for item in multi_chunk_result.keys():
                if not multi_chunk_result[item]:
                    raise RuntimeError("%s 块上传已失败" % myKeyName)
            # 总结都OK，完成上传做合并动作
            mp.complete_upload()
            print("%s 大文件上传成功" % myKeyName);
            # 此时需要回调渲染
            newTh = threading.Thread(target=self.file_list, args=(self.fileList_tableView, ));
            newTh.start();
            return "%s 大文件上传成功" % myKeyName
        except BaseException as e:
            print("%s 大文件上传失败，原因:%s" % (myKeyName, e))
            if mp:
                mp.cancel_upload()
            raise e

    # 线程执行时间
    def thread_time(target):
        def wrapper(*args, **kwargs):
            st = time.time()
            try:
                return target(*args, **kwargs)
            finally:
                et = time.time()
                print(et - st)
                threading.currentThread().duration = et - st
        return wrapper

    # 下载文件
    def download_file(self, filePath: str):
        keys = self.bucket.get_key(filePath);
        abfilePath="D:\\"+filePath.replace(self.current_bucket_name, "")
        with open(abfilePath, "wb") as file:
            while True:
                content = keys.read(4096);
                file.write(content)
                if not content:
                    break;

    # 实现粘贴板
    def copy_link(self, filePath: str):
        # 清空剪贴板
        pyperclip.copy('');
        filePath = KS3_LINK+filePath;
        pyperclip.copy(filePath);
        QMessageBox.information(None, "信息提示", "复制链接成功...");

    # 获取视频时长
    def video_duration_3(self, filename: str):
        cap = cv2.VideoCapture(KS3_LINK + filename)
        if cap.isOpened():
            rate = cap.get(5)
            frame_num = cap.get(7)
            duration = frame_num / rate
            return duration
        return -1

    # 读取本地文件的视频时长
    def video_duration_local(self, filePath: str):
        cap = cv2.VideoCapture(filePath);
        length = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        fps = int(cap.get(cv2.CAP_PROP_FPS))
        duration = length / fps
        cap.release();
        return duration;

    # 生成文件id
    def generate_file_id(self):
        return "{0}{1}".format(time.strftime('%Y%m%d%H%M%S'), str(random.randint(1000,9999)))

    # 更新进度条
    def call_progressui(self, val):
        current = self.progressBar.value();
        self.progressBar.setValue(current+val);

# 主窗体
class ZtjsWindow(QWidget):
    """
    课件上传工具
    """
    def __init__(self):
        super(ZtjsWindow, self).__init__();
        self.init_ui();

    # 初始化UI
    def init_ui(self):
        self.ui=uic.loadUi("./ui/ztjs_material.ui")
        self.upload_btn = self.ui.upload_btn;
        self.filePath = self.ui.txtEdit_filePath;
        self.file_progressBar = self.ui.file_progressBar;
        self.fileList_tableView = self.ui.fileList_tableWidget;
        #初始化两外一个窗体
        self.preview_win = child_video_preview();
        #设置列宽
        self.fileList_tableView.setColumnWidth(0, 180);
        self.fileList_tableView.setColumnWidth(1, 440);
        self.fileList_tableView.setColumnWidth(2, 86);
        self.fileList_tableView.setColumnWidth(3, 76);
        self.fileList_tableView.setColumnWidth(4, 130);
        self.fileList_tableView.setColumnWidth(5, 40);
        self.fileList_tableView.setColumnWidth(6, 40);
        self.fileList_tableView.setColumnWidth(7, 60);
        self.upload_btn.clicked.connect(self.selectFile);
        # 初始化金山云
        jinshanyun = ks3();
        self.jinshanyun = jinshanyun;
        self.jinshanyun.file_list(self.fileList_tableView)  #  展示所有的文件集合
        #设置点击事件
        self.fileList_tableView.itemClicked.connect(self.file_preview);

    def selectFile(self):
        # 选择文件按钮
        filePaths, fileType = QFileDialog.getOpenFileNames(self, "请选择一个素材", "", "*.mp4;;*.avi;;All Files(*)");
        filePath = filePaths[0];
        if len(filePath)>0 :
            self.filePath.setText(filePath);
            #self.jinshanyun.uploadFile(filePath, self.file_progressBar);
            # 开启独立线程
            th = threading.Thread(target=self.jinshanyun.uploadFile, args=(filePath, self.file_progressBar, self.fileList_tableView));
            th.start();
        else:
            self.filePath.setText("请选择正确的课件");
            return;

    # 视频文件预览
    def file_preview(self, item):
        txt = item.text();
        if txt=="预览":
            # 预览
            self.preview_win.Open(item.whatsThis().replace(self.jinshanyun.current_bucket_name, ""), KS3_LINK+item.whatsThis());
        elif txt=="下载":
            #下载
            download_thread = threading.Thread(target=self.jinshanyun.download_file, args=(item.whatsThis(), ))
            download_thread.start();
            abfilePath="D:\\"+item.whatsThis().replace(self.jinshanyun.current_bucket_name, "");
            QMessageBox.information(None, "信息提示", "文件已经下载到{filePath}，请注意查收...".format(filePath=abfilePath));
        elif txt=="复制链接":
            # 复制链接
            self.jinshanyun.copy_link(item.whatsThis());

    # 更新进度条
    def call_progressui(self, progresstxt):
        self.progressBar.setValue(progresstxt)

# 预览
class child_video_preview(QMainWindow, videoPlayer):
    def __init__(self, parent=None):
        super(child_video_preview, self).__init__(parent);

    def Open(self, fileName, filePath):
        self.ui.show();
        self.open(fileName, filePath);




if __name__ == '__main__':
    app = QApplication(sys.argv);
    widget = ZtjsWindow()
    t1 = threading.Thread(target=widget.ui.show())
    t1.start()
    sys.exit(app.exec_())